﻿using Bettzueche.RLLibrary.Functions;
using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace AMEE1
{
    struct Daten
    {
        public int Schrittzahl;
        public double Vergeltung;

        public Daten(int schritte, double lohn) {
            Schrittzahl = schritte;
            Vergeltung = lohn;
        }
    }

    class Program
    {
        static void Main(string[] args) {
            // DoMapConfExample();
            // DoSimuSensExample();
            // DoEnvironmentExample();
            
            Environment env;
            Agent agent;
            InitRLKomponenten(out env, out agent);
            Task task = new Task(env, agent);
            //task.MaxSteps = 5000; // = Standard
            //env.ZielBelohnung = 0.0;

            List<Daten> daten = new List<Daten>();
            daten.Add(new Daten(task.MaxSteps, env.ZielBelohnung));
            int stepCount;
            double reward;
            ConsoleKeyInfo keyInfo = new ConsoleKeyInfo();
            int anzahl;
            do {
                Console.WriteLine("Wie viele Episoden? ");
                bool parsed = Int32.TryParse(Console.ReadLine(), out anzahl);
                if (!parsed)
                    continue;
                for (int i = 0; i < anzahl; i++) {
                    task.RunTask(out stepCount, out reward);
                    Console.WriteLine("Schritte: {0,4} / Belohnung: {1:F3}", stepCount, reward);
                    daten.Add(new Daten(stepCount, reward));
                }
                Console.Write("Nochmal? [j/n] ");
                keyInfo = Console.ReadKey(false);
                Console.WriteLine();
            } while (ConsoleKey.J.Equals(keyInfo.Key));
            string path = SafeAsCSV(daten,agent);
            Console.WriteLine("Ergebnisse in csv gespeichert:\n  "+path);
            Console.WriteLine("\n Taste zum Beenden drücken...");
            Console.ReadKey(true);
        }


        static string SafeAsCSV(List<Daten> daten, Agent agent) {
            StringBuilder sb = new StringBuilder();
            DateTime date = DateTime.Now;
            string dateString = date.ToString("yyMMdd_HH-mm-ss");
            sb.AppendLine("Datum:" + date.ToShortDateString());
            sb.AppendLine("Maximale Schrittzahl: "+daten[0].Schrittzahl);
            sb.AppendLine("Zielbelohnung: "+ daten[0].Vergeltung);
            sb.AppendLine("WindowSize = 10").AppendLine();
            sb.AppendLine("EpBundle;Schritte;Belohnung;");
            Daten d = new Daten();
            Daten dMean = new Daten();
            for (int i = 1; i < daten.Count; i++) {
                // immer 10 Mitteln (wie bei Dampfross)
                d = daten[i];
                dMean.Schrittzahl += d.Schrittzahl;
                dMean.Vergeltung += d.Vergeltung;
                if (i%10 == 0) {
                    dMean.Schrittzahl = (int)Math.Round(dMean.Schrittzahl / 10.0);
                    dMean.Vergeltung /= 10.0;
                    sb.AppendFormat("{0,4};{1,4};{2:F2};", i/10, dMean.Schrittzahl, dMean.Vergeltung).AppendLine();
                    dMean = new Daten();
                }
            }
            sb.AppendLine();
            sb.AppendLine("Alpha="+agent.LearnRate);
            sb.AppendLine("Gamma="+agent.DiscountFactor);
            sb.AppendLine("Lambda="+agent.TraceDecay);
            sb.AppendLine("Epsilon="+agent.Epsilon);
            // Datei (Name?)
            if (!Directory.Exists("Experimente")) {
                Directory.CreateDirectory("Experimente");
            }
            string fileName = @"Experimente\exp_" + dateString + ".csv";
            File.WriteAllText(fileName, sb.ToString());
            FileInfo fi = new FileInfo(fileName);
            return fi.FullName;
        }

        /// <summary>
        /// Initializes MapConfig mit 4 Sektoren, 2 Spuren (je 0.5m).<para>
        /// Sensor mir Weltfunktion2.</para><para>
        /// Reward = StandardVergeltung.</para><para>
        /// uTiling mit 1/8 Auflösung und 4 Tilings (gesamt 8 Tiles)</para>
        /// </summary>
        static void InitRLKomponenten(out Environment env, out Agent agent) {
            // Tilings-Organisation der Unebenheit:
            //double precision = 1.0 / Math.Pow(2, 3); // 8 unterscheidbare Unterteilungen des Wertebereichs
            double precision = 0.125;
            int tilings = 1; //(int)Math.Pow(2, 0);
            MultiTiling uTiling = new MultiTiling(tilings, precision, min: 0.0, max: 1.0);
            
            // Konfiguration der Roboterkarte:
            int anzSektoren = 8;
            //float[] spurRadien = { 0.5f, 1.0f }; // zwei Spuren
            float[] spurRadien = { 1.0f, 2.0f };
            //int[] entfernungen = { 1, 2, 3, 4, 5, 10, 20, 40, 80, Int32.MaxValue};
            int[] entfernungen = { 1, 2, 3, 4, 6, 9, 16, 32, 64, Int32.MaxValue };
            Entfernungen entfTiling = new Entfernungen(entfernungen);
            var weltConfig = new MapConfiguration(uTiling, entfTiling, anzSektoren, spurRadien);
            // 8 UIntervale, 8 Abschnitte, 8 Richtungen, 8 Entfernungen = 8^10 Merkmale je Aktion (8 Stück)
            // -> 1.073.741.824 * 8 = 8.589.934.592 WETupel * 2*8Byte = bis zu (8^13=) 137.438.953.472 Byte = 128 GB
            
            // Sensor mit der "Realwelt":
            BumpinessFunction realWelt = HilfsFunktionen.Weltfunktion2;
            SimuSensor sensor = new SimuSensor(weltConfig, realWelt);

            // Umwelt inkl. Reward-Berechnung:
            //Vergeltung sReward = new Vergeltung { Limit=10.0, Steilheit=8.0/3.0, Wendepunkt=new Vector(.0, .0) };
            Vergeltung uReward = new Vergeltung { Limit=1.0, Steilheit=8.0, Wendepunkt=new Vector(.5, 2.0) };
            Vergeltungssunktion rewardFunc = (s, u) => -(1 + uReward.Eval(u));
            env = new Environment(sensor, rewardFunc);
            // DimensionCombination combi = new DimensionCombination(weltConfig);
            // env = new Environment(sensor, uReward, 0.0, 0.0, DimensionCombination combi) {
            
            // Lern-Agent mit zugehöriger "Schätzfunktion":
            QEFunktion qe = new QEFunktion(weltConfig.Aktionen);
            agent = new Agent(env, qe);

            // optionale Parameter:
            env.BumpinessBound = 0.95; // maximale Unebenheit
            env.BoundPenalty = -3.0; // normale Max-Bestrafung ist -3
            env.ZielBelohnung = 1000;//0.0;
            agent.Epsilon = 0.03;
            agent.LearnRate = 0.3;
            agent.TraceDecay = 0.7;
            agent.DiscountFactor = 1.0;
            qe.MinE = Math.Pow(agent.DiscountFactor*agent.TraceDecay, 42);

        }

        




        static void DoMapConfExample() {
            // Karte mit zwei Spuren und vier Sektoren.
            // Insgesamt 8 Abschnitte
            int anzSektoren = 4;
            float[] spurRadien = { 0.5f, 1.0f }; // zwei Spuren, jeweil der Breite 0.5

            double precision = 1.0 / Math.Pow(2, 8); // 8 unterscheidbare Unterteilungen des Wertebereichs
            int tilings = (int)Math.Pow(2, 2);
            MultiTiling uTiling = new MultiTiling(tilings, precision, min: 0.0, max: 1.0);
            int[] entfernungen = { 1, 2, 3, 4, 5, 10, 20, 40, 80, Int32.MaxValue };
            Entfernungen entfTiling = new Entfernungen(entfernungen);

            MapConfiguration weltConfig = new MapConfiguration(uTiling, entfTiling, anzSektoren, spurRadien);
            for (int spur = 0; spur < 2; spur++) {
                for (int sektor = 0; sektor < 4; sektor++) {
                    int abschnittsNummer = weltConfig.GibAbschnittsnummer(spur, sektor);
                    Console.WriteLine("Abschnitt Nummer: {0} (Sp: {1} Se: {2}", abschnittsNummer, spur, sektor);
                }
            }
        }

        static void DoSimuSensExample() {
            int anzSektoren = 4;
            float[] spurRadien = { 0.5f, 1.0f }; // zwei Spuren

            double precision = 1.0 / Math.Pow(2, 8); // 8 unterscheidbare Unterteilungen des Wertebereichs
            int tilings = (int)Math.Pow(2, 2);
            MultiTiling uTiling = new MultiTiling(tilings, precision, min: 0.0, max: 1.0);
            int[] entfernungen = { 1, 2, 3, 4, 5, 10, 20, 40, 80, Int32.MaxValue };
            Entfernungen entfTiling = new Entfernungen(entfernungen);

            MapConfiguration weltConfig = new MapConfiguration(uTiling, entfTiling, anzSektoren, spurRadien);
            BumpinessFunction realWelt = HilfsFunktionen.Weltfunktion2;

            SimuSensor sensor = new SimuSensor(weltConfig, realWelt);

            double xKoord = -5.0;
            double yKoord = 10.0;
            double[] unebenheiten = sensor.Messe(xKoord, yKoord);

            int abNr = weltConfig.GibAbschnittsnummer(spur: 1, sektor: 3);
            double u7 = unebenheiten[abNr];
            Console.WriteLine("Unebenheit in Abschnitt {0}: {1:N}", abNr, u7);
        }

        static void DoEnvironmentExample() {
            int anzSektoren = 4;
            float[] spurRadien = { 0.5f, 1.0f }; // zwei Spuren

            double precision = 1.0 / Math.Pow(2, 8); // 8 unterscheidbare Unterteilungen des Wertebereichs
            int tilings = (int)Math.Pow(2, 2);
            MultiTiling uTiling = new MultiTiling(tilings, precision, min: 0.0, max: 1.0);
            int[] entfernungen = { 1, 2, 3, 4, 5, 10, 20, 40, 80, Int32.MaxValue };
            Entfernungen entfTiling = new Entfernungen(entfernungen);

            MapConfiguration weltConfig = new MapConfiguration(uTiling, entfTiling, anzSektoren, spurRadien);
            BumpinessFunction realWelt = HilfsFunktionen.Weltfunktion2;

            SimuSensor sensor = new SimuSensor(weltConfig, realWelt);
            Vergeltung sReward = new Vergeltung { Limit=10.0, Steilheit=8.0/3.0, Wendepunkt=new Vector(.0, .0) };
            Vergeltung uReward = new Vergeltung { Limit=10.0, Steilheit=-10.0, Wendepunkt=new Vector(.5, -10.0) };
            Vergeltungssunktion rewardFunc = (s, u) => sReward.Eval(s) + uReward.Eval(u);

            Environment env = new Environment(sensor, rewardFunc);

            State state = env.State; // aktueller Zustand
            Console.WriteLine("Aktueller Zustand:\n " + state.ToString());

            // Aktion "Gehe um 0.2 Richtung X-Achse und -0.2 Richtung Y-Achse"
            Action act = new Action { X = 0.75, Y = -0.75 };
            double reward = env.DoAction(act, out state); // gibt reward und next-state
            Console.WriteLine("Neuer Zustand:\n " + state.ToString());
            Console.WriteLine("Bewertung der letzten Aktion: {0:F3}", reward);
        }

    }
}
